package com.sfc.sdsr.server.dao;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;

import com.sfc.sdsr.common.model.Address;
import com.sfc.sdsr.common.model.Event;
import com.sfc.sdsr.common.model.IFileDao;
import com.sfc.sdsr.common.model.News;
import com.sfc.sdsr.common.model.Person;
import com.sfc.sdsr.common.model.Song;

public class FileDao implements Dao {

	private final Logger log = Logger.getLogger(getClass());
	private final static Logger logStatic = Logger.getLogger(FileDao.class);

	private static Dao instance = null;
	private File root = null;

	private File personTable;
	private File eventTable;
	private File newsTable;
	private File songTable;
	private File addressTable;
	
	private FileDao() throws Exception {	
		String userHome = System.getProperty("user.home");

		String sfcFdbDir = "SfcAppServer";
		String personDir_s = "PERSON";
		String eventDir_s = "EVENT";
		String newsDir_s = "NEWS";
		String songDir_s = "SONGS";
		String addressDir_s = "ADDRESS";
		
		root = new File(userHome,sfcFdbDir);
		personTable = new File(root,personDir_s);
		eventTable = new File(root,eventDir_s);
		newsTable = new File(root,newsDir_s);
		songTable = new File(root,songDir_s);
		addressTable = new File(root,addressDir_s);
		
		createDir(root);
		createDir(personTable);
		createDir(eventTable);
		createDir(newsTable);
		createDir(songTable);
		createDir(addressTable);
		
		log.info("Successfully initalized file database at: " + root.getAbsolutePath());
	}
	
	public synchronized static Dao getInstance() {
		try {
			instance = new FileDao();
		} catch (Exception e) {
			logStatic.error("Failed to initalize file based database: " + e.getMessage());
			instance = BrokenDao.getInstance("Failed to initalize file based database: " + e.getMessage());
		}
		return instance;
	}

	/**
	 * http://en.wikipedia.org/wiki/Java_hashCode()
	 * 
	 * @param primaryKey
	 * @return the hash code of the given primaryKey
	 */
	public static int calculateIndex(String primaryKey) {
        int hashCode = 0;
        int len = primaryKey.length();
        for (int i = 0; i < len; i++) {
            hashCode = 31 * hashCode + primaryKey.charAt(i);
        }
        return hashCode;
	}
	
	@Override
	public synchronized boolean createEvent(Event event) {
		return createData(eventTable,event);
	}

	@Override
	public synchronized boolean createMember(Person person) {
		return createData(personTable, person);
	}

	@Override
	public synchronized boolean createNews(News news) {
		return createData(newsTable, news);
	}

	@Override
	public synchronized boolean createSong(Song song) {
		return createData(songTable, song);
	}

	@Override
	public synchronized boolean createAddress(Address address) {
	    return createData(addressTable, address);
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public List<Event> getEvents() {
		return (List<Event>) getItems(eventTable);
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Person> getMembers() {
		return (List<Person>) getItems(personTable);
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<News> getNews() {
		return (List<News>) getItems(newsTable);
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Song> getSongs() {
		return (List<Song>) getItems(songTable);
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<Address> getAddresses() {
	    return (List<Address>) getItems(addressTable);
	}
	
	/**
	  * creates the dir if it doesn't exist
	  * 
	  * @param newDir the directory to be created
	  * @throws Exception if the dir didn't exist and couldn't be created
	  */
	private void createDir(File newDir) throws Exception {
		if (!newDir.exists()) {
			if (!newDir.mkdir()) {
				throw new Exception("Faild to create Server root Database Directory. folder: " + newDir.getName() + " in dir: " + newDir.getPath());
			}
			log.info(newDir.getAbsoluteFile() + " Created");
		}
		
	}

	private synchronized boolean createData(File table, IFileDao iFileDao) {
	    String primaryKey = iFileDao.getPrimaryKey();
	    int index = FileDao.calculateIndex(primaryKey);
	    File newEvent = new File(table,String.valueOf(index));
	    
	    if (newEvent.exists()) {
	        log.error("Can't create, item already exists: " + iFileDao);
	        return false;
	    }
	    
		ObjectOutputStream oos = null;
		try {
		    oos = new ObjectOutputStream(new FileOutputStream(newEvent));
	        oos.writeObject(iFileDao);
		} catch (IOException ioe) {
		    log.error("Can't write to file system: " + ioe.getMessage(), ioe);
		    return false;
		} finally {
		    if (oos != null) {
                try {
                    oos.close();
                } catch (IOException ioe) {
                    log.error("Failed to close Object Output Stream: " + ioe.getMessage(), ioe);
                }
		    }
		}
		
		log.info("Sucessfully created " + iFileDao.toString());
		return true;
	}
	
	private List<? extends IFileDao> getItems(File table) {
		File[] items = table.listFiles();
		List<IFileDao> returnItems = new ArrayList<IFileDao>();
		
	    if (items == null) {
	        log.error("table was not found in the file system: " + table.getAbsoluteFile());
	        return null;
	    }
	    
	    for (File item : items) {
            IFileDao currentIFileDao;
            ObjectInputStream ois = null;
            try {
                ois = new ObjectInputStream(new FileInputStream(item));
                currentIFileDao = (IFileDao)ois.readObject();
                returnItems.add(currentIFileDao);
            } catch (FileNotFoundException fnfe) {
                log.error("File Not Found: " + item.getAbsoluteFile(), fnfe);
            } catch (IOException ioe) {
                log.error("Failed to read object from file: " + item.getAbsoluteFile(), ioe);
            } catch (ClassNotFoundException cnfe) {
                log.error("Class Not Found Exception: " + item.getAbsoluteFile(), cnfe);
            } finally {
                try {
                    ois.close();
                } catch (IOException ioe) {
                    log.error("Failed to close Object Input Stream: " + ioe.getMessage(),ioe);
                }
            }
	    }
		
	    return returnItems;
	}
	
}